Skip to content

feat: module replacements v3#2068

Open
gameroman wants to merge 91 commits intonpmx-dev:mainfrom
gameroman:module-replacements-v3
Open

feat: module replacements v3#2068
gameroman wants to merge 91 commits intonpmx-dev:mainfrom
gameroman:module-replacements-v3

Conversation

@gameroman
Copy link
Copy Markdown
Contributor

@gameroman gameroman commented Mar 13, 2026

Module replacements v3

Simple

image

image

Native

image

Documented

image

Removal

image

Compare page

image

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 29, 2026 11:03pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 29, 2026 11:03pm
npmx-lunaria Ignored Ignored Mar 29, 2026 11:03pm

Request Review

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 82.97872% with 8 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
app/components/Compare/ReplacementSuggestion.vue 85.00% 2 Missing and 1 partial ⚠️
app/components/Package/Replacement.vue 85.71% 2 Missing and 1 partial ⚠️
app/composables/useModuleReplacement.ts 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@gameroman
Copy link
Copy Markdown
Contributor Author

image

@gameroman
Copy link
Copy Markdown
Contributor Author

image

@gameroman
Copy link
Copy Markdown
Contributor Author

image

Copy link
Copy Markdown

@joaopedrodcf joaopedrodcf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hope you guys don't mind me trying to review the PR 🙏

})

const replacementDescription = useMarkdown(() => ({
text: (props.replacement as { description?: string }).description ?? '',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of using a type guard instead of relying in casting ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description is either undefined or a string so it can't be anything else

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think he means this which is safer than casting:

const description = props.replacement.type !== 'preferred' ? props.replacement.description : undefined;
return {text: description ?? ''};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah exactly trying to make the code more typesafe to refactors etc

<code>{{ replacement.replacementModule }}</code>
</template>
<template #community>
<a
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use NuxtLink but what do you think ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think NuxtLink is for internal links?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also works for external links as far as I know

https://nuxt.com/docs/4.x/api/components/nuxt-link#external-routing

Comment on lines +887 to +891
<PackageReplacement
v-if="moduleReplacement"
:mapping="moduleReplacement.mapping"
:replacement="moduleReplacement.replacement"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we always have to options parent fetchs and decides if renders or not or we put that logic inside the component and inside we have a v-if to not render html etc

Maybe you can even have a component packageReplacementIntegration that is responsible to fetch the replacement and the rendering the visual part

})

const replacementDescription = useMarkdown(() => ({
text: (props.replacement as { description?: string }).description ?? '',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably just have a shared function in this repo that computes the description:

function getReplacementDescription(replacement) {
  switch (replacement.type) {
    case 'documented':
      return undefined;
    default:
      return replacement.description;
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that be in module-replacements itself since it might be useful for other consumers too or just here?

try {
const replacement = await $fetch<ModuleReplacement | null>(`/api/replacements/${name}`)
const response = await $fetch<{
mapping: ModuleReplacementMapping
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@danielroe iirc you said these types are inferred based on the route. is that true? does this not infer them because of the interpolation?

replacements: readonly(replacements),
noDepSuggestions: readonly(noDepSuggestions),
infoSuggestions: readonly(infoSuggestions),
replacements,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these still readonly? was that dropped on purpose?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as far as I can see seems by mistake,

(event): { mapping: ModuleReplacementMapping; replacement: ModuleReplacement } | null => {
const pkg = getRouterParam(event, 'pkg')
if (!pkg) return null
const mapping = all.mappings[pkg]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a security problem? could i pass pkg as "constructor" or something funky?

"example": "Example:",
"native": "This can be replaced with {replacement}, available since Node {nodeVersion}.",
"simple": "The {community} has flagged this package as redundant, with the advice: {replacement}.",
"native_no_version": "This can be replaced with {replacement}.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"native_no_version": "This can be replaced with {replacement}.",
"native_no_version": "This package can be replaced with {replacement}.",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants